package com.dao;

import java.beans.PropertyDescriptor;
import java.lang.reflect.ParameterizedType;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import com.tool.JdbcTool;

/**
 * 介绍：
 *    dao层的祖先类,供其它Dao类继承
 **************************************************************************************************
 * 特点：
 *    ①该类是一个抽象类,从而该类不能new出实例,只能让其子类来new出实例
 *    ②该类带有一个泛型参数T,在运行期间,通过“反射泛型”,从而得到泛型参数T的实际类型
 *    ③该类中的方法都是非抽象方法,且都被final修饰,从而禁止其子类重写这些方法
 *    ④该类的构造函数,其参数表示要操作的是哪张数据库表
 **************************************************************************************************
 * 作用：
 *    对数据库的某张表进行操作（增、删、改、查）.但有前提要求：
 *        Ⅰ通过构造函数的“参数传递”来指明“要操作的数据库表”
 *        Ⅱ“泛型T实例中的属性名”与“要操作的数据库表中的字段名”一致
 **************************************************************************************************       
 * 涉及技术：
 *    ①泛型
 *    ②反射
 *    ③数据库的元数据类型（用途：获取表的主键字段名,获取表的各个字段名,获取表的各个字段的类型）
 *    ④便捷操作javabean对象（举例：获取指定属性的值、给指定的属性赋值、填充属性）
 **************************************************************************************************   
 * 用到的第三方库：
 *    ①Apache Commons DbUtils
 *        Ⅰcommons-dbutils-1.6.jar
 *    ②Apache Commons BeanUtils（依赖：Apache Commons Logging、Apache Commons Collections）
 *        Ⅰcommons-beanutils-1.9.3.jar
 *        Ⅱcommons-logging-1.2.jar
 *        Ⅲcommons-collections-3.2.2.jar
 */
public abstract class AncestorDao<T>
{
	
	protected QueryRunner qr = new QueryRunner(true);  //某些数据库（SQLServer2008）如果没有这个true参数,则执行插入语句时会报错
	
	private Class<T> clazz;  //泛型T的类型
	
	protected String tableName;  //数据库表名
	
	
	
	@SuppressWarnings("unchecked")
	protected AncestorDao(String tableName)
	{
		clazz = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];  //反射泛型
		//tableName = clazz.getSimpleName().toLowerCase();
		this.tableName = tableName;
	}
	
	
	
	
	
	/**
	 * 查询一个：根据“主键”查询一条记录（前提条件：仅适用于表的主键字段只有一个的情况,不适用于表被设计为多个字段作为联合主键的情况）
	 */
	public final T queryOne(String primaryKeyColumnValue)
	{
		Connection conn = JdbcTool.getConnection();
		try
		{
			DatabaseMetaData databaseMetaData = conn.getMetaData();
			ResultSet primaryKeyResultSet = databaseMetaData.getPrimaryKeys(null, null, tableName);
			boolean bool = primaryKeyResultSet.next();
			String primaryKeyColumnName = "id";
			if(bool)
			{
				primaryKeyColumnName = primaryKeyResultSet.getString("COLUMN_NAME");  //得到主键字段的列名称
			}
			String sql = String.format("select * from %s where %s = ?", tableName, primaryKeyColumnName);
			System.err.println("sql："+sql);
			return qr.query(conn, sql, new BeanHandler<T>(clazz), primaryKeyColumnValue);
		}
		catch(SQLException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	
	
	
	/**
	 * 查询全部：查询全部记录
	 */
	public final List<T> queryAll()
	{
		String sql = String.format("select * from %s", tableName);
		System.err.println("sql："+sql);
		Connection conn = JdbcTool.getConnection();
		try
		{
			return qr.query(conn, sql, new BeanListHandler<T>(clazz));
		}
		catch(SQLException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	
	
	
	/**
	 * 查询任意：根据指定的“字段名”与“字段值”进行查询
	 */
	public final List<T> queryAny(String columnName, Object columnValue)
	{
		String sql = String.format("select * from %s where %s=?", tableName, columnName);
		System.err.println("sql："+sql);
		try
		{
			Connection conn = JdbcTool.getConnection();
			return qr.query(conn, sql, new BeanListHandler<T>(clazz), columnValue);
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	
	
	
	/**
	 * 插入一个：插入一条记录（细节：一定要记得判断,获取到的表字段名,在bean对象中是否也存在与之同名的属性）
	 */
	public final void insertOne(T t)
	{
		Connection conn = JdbcTool.getConnection();
		try
		{
			DatabaseMetaData databaseMetaData = conn.getMetaData();
			ResultSet columnResultSet = databaseMetaData.getColumns(null, null, null, null);
			LinkedList<String> columnNameList = new LinkedList<String>();  //此集合用来存储“指定的表”的每一列的列名
			while(columnResultSet.next())
			{
				String tn = columnResultSet.getString("TABLE_NAME");    //获取表名
				if(tn.equalsIgnoreCase(this.tableName))
				{
					String columnName = columnResultSet.getString("COLUMN_NAME");  //获取表的每一列的列名
					PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(t, columnName);  //获取bean对象的属性描述器,若不存在该属性,则返回null.用于判断bean对象中是否也存在与该列名同名的属性					
					if(pd != null)
					{
						columnNameList.add(columnName);
					}
				}
			}
			//动态构造sql语句
			StringBuilder sb1 = new StringBuilder("insert into "+tableName+"(");
			StringBuilder sb2 = new StringBuilder("values(");
			for(String columnName : columnNameList)
			{
				sb1.append(columnName+",");
				sb2.append("?,");
			}
			sb1.deleteCharAt(sb1.lastIndexOf(","));
			sb1.append(") ");
			sb2.deleteCharAt(sb2.lastIndexOf(","));
			sb2.append(")");
			String sql = sb1.append(sb2).toString();
			System.err.println("sql："+sql);
			Object[] params = new Object[columnNameList.size()];
			for(int i=0; i<params.length; i++)
			{
				params[i] = PropertyUtils.getProperty(t, columnNameList.get(i));  //用Apache Commons BeanUtils框架的PropertyUtils工具类获取bean对象的属性值
			}
			qr.insert(conn, sql, new ColumnListHandler<Object>(), params);
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	
	
	
	/**
	 * 删除一个：根据“主键”删除一条记录（前提条件：仅适用于表的主键字段只有一个的情况,不适用于表被设计为多个字段作为联合主键的情况）
	 */
	public final void deleteOne(String primaryKeyColumnValue)
	{
		Connection conn = JdbcTool.getConnection();
		try
		{
			DatabaseMetaData databaseMetaData = conn.getMetaData();
			ResultSet rs = databaseMetaData.getPrimaryKeys(null, null, tableName);
			rs.next();
			String primaryKeyColumnName = rs.getString("COLUMN_NAME");  //得到主键字段的列名称
			String sql = String.format("delete from %s where %s = ?", tableName, primaryKeyColumnName);
			System.err.println("sql："+sql);
			qr.update(conn, sql, primaryKeyColumnValue);
		}
		catch(SQLException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	
	
	
	/**
	 * 修改一个：修改一条记录（细节：一定要记得判断,获取到的表字段名,在bean对象中是否也存在与之同名的属性）
	 */
	public final void updateOne(T t)
	{
		Connection conn = JdbcTool.getConnection();
		try
		{
			DatabaseMetaData databaseMetaData = conn.getMetaData();
			ResultSet primaryKeyResultSet = databaseMetaData.getPrimaryKeys(null, null, tableName);
			primaryKeyResultSet.next();
			String primaryKeyColumnName = primaryKeyResultSet.getString("COLUMN_NAME");  //得到主键字段的列名称
			ResultSet columnResultSet = databaseMetaData.getColumns(null, null, tableName, null);
			LinkedList<String> columnNameList = new LinkedList<String>();  //此集合用来存储表的每一列的列名
			while(columnResultSet.next())
			{
				String columnName = columnResultSet.getString("COLUMN_NAME");  //获取表的每一列的列名
				PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(t, columnName);  //获取bean对象的属性描述器,若不存在该属性,则返回null.用于判断bean对象中是否也存在与该列名同名的属性
				if(pd != null)
				{
					columnNameList.add(columnName);
				}
			}
			columnNameList.remove(primaryKeyColumnName);  //排除主键字段
			//动态构造sql语句
			StringBuilder sb1 = new StringBuilder(String.format("update %s set ", tableName));
			StringBuilder sb2 = new StringBuilder(String.format("where %s=?", primaryKeyColumnName));
			for(String columnName : columnNameList)
			{
				sb1.append(columnName+"=?, ");
			}
			sb1.deleteCharAt(sb1.lastIndexOf(","));
			String sql = sb1.append(sb2).toString();
			System.err.println("sql："+sql);
			columnNameList.addLast(primaryKeyColumnName);  //把移除的主键字段加回来
			Object[] params = new Object[columnNameList.size()];
			for(int i=0; i<params.length; i++)
			{
				params[i] = PropertyUtils.getProperty(t, columnNameList.get(i));  //用Apache Commons BeanUtils框架的PropertyUtils工具类获取bean对象的属性值
			}		
			qr.update(conn, sql, params);
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	
	
	
	/**
	 * 判断是否已存在：根据给定的“字段名”和“字段值”进行查询,查看表中是否存在对应的记录.若存在则返回true,若不存在则返回false
	 * 应用场景：具有“唯一约束”的字段,在插入或修改数据之前,通常需要判断要插入或修改的值是否已存在,例如判断account是否已存在
	 */
	public final boolean existedColumnValue(String columnName, Object columnValue)
	{
		String sql = String.format("select * from %1$s where %2$s=?", tableName, columnName);
		System.err.println("sql："+sql);
		try
		{
			Connection conn = JdbcTool.getConnection();
			Object value = qr.query(conn, sql, new ScalarHandler<Object>(), columnValue);
			if(value != null)
			{
				return true;
			}
			return false;
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}
	
	
	
}